#  Copyright (c) 2010
#  The Regents of the University of Michigan
#  All Rights Reserved

#  Permission is granted to use, copy, create derivative works, and
#  redistribute this software and such derivative works for any purpose,
#  so long as the name of the University of Michigan is not used in
#  any advertising or publicity pertaining to the use or distribution
#  of this software without specific, written prior authorization. If
#  the above copyright notice or any other identification of the
#  University of Michigan is included in any copy of any portion of
#  this software, then the disclaimer below must also be included.

#  This software is provided as is, without representation or warranty
#  of any kind either express or implied, including without limitation
#  the implied warranties of merchantability, fitness for a particular
#  purpose, or noninfringement.  The Regents of the University of
#  Michigan shall not be liable for any damages, including special,
#  indirect, incidental, or consequential damages, with respect to any
#  claim arising out of or in connection with the use of the software,
#  even if it has been or is hereafter advised of the possibility of
#  such damages.
from pydht import dhtutil
import gnutella
import serialization

import event

import array
import bisect
import collections
import hashlib
import logging
import random
import socket
import time


class MojitoError(Exception): pass

MAX_RETRIES = 1

# Pulled from my limewire.props; cross-check with someone else's. Provenance is unclear.
BOOTSTRAP_NODES = [('38.108.107.68', 6002)]

NAME = 'Mojito'

class DHTClient(dhtutil.BaseClient):
  '''One instance of a client for the Mojito DHT.'''


  def __init__(self, address, dht_id=None, firewalled=False):
    dhtutil.BaseClient.__init__(self, address)
    self.firewalled = firewalled
    self.instance_id = 1
    self.dht_id = dht_id if dht_id else open('/dev/urandom').read(20)
    self.this_contact = make_contact(address, self.dht_id)

  def _build_request_header(self, action):
    header = serialization.MojitoHeader(
      action,
      self.this_contact,
      self.instance_id,
      serialization.FIREWALLED if self.firewalled else 0,
      0)
    return header

  _build_reply_header = _build_request_header

  def _handle_recv(self):
    packet, sender = self.sock.recvfrom(8192)
    try:
      # In Mojito, the serialization code generates a fully filled out
      # Contact and passes it to all handlers.
      serialization.decode_packet(packet, self, sender)
    except serialization.SerializationError as e:
      logging.error('Bad packet from %s:%d: %s' % (sender[0], sender[1], e))
    return True  # Reschedule.

  def send_ping_request(self, contact):
    request = serialization.RequestPing(
        self._build_request_header(serialization.ACT_REQUEST_PING))
    request = serialization.encode_ping_request(request)
    self._send(request, contact.address, contact)

  def send_ping_reply(self, contact):
    reply = serialization.ReplyPing(
        self._build_reply_header(serialization.ACT_REPLY_PING))
    reply = serialization.encode_ping_reply(reply)
    self._send(reply, contact.address, contact)

  def send_store_reply(self, contact):
    reply = serialization.ReplyStore(
        self._build_reply_header(serialization.ACT_REPLY_STORE),
        [])
    reply = serialization.encode_store_reply(reply)
    self._send(reply, contact)

  def send_find_node(self, contact, id_to_find):
    request = serialization.RequestFindNode(
        self._build_request_header(serialization.ACT_REQUEST_FIND_NODE),
        id_to_find)
    request = serialization.encode_find_node_request(request)
    self._send(request, contact.address, contact)


brute_force_id_with_bits = dhtutil.fake_brute_force_id_with_bits


def compute_node_id(contact):
  return contact.id

def get_contact_address(contact):
  return contact.address

gnutella_VENDOR = gnutella.VENDOR
gnutella_VERSION = gnutella.VERSION
def make_contact(addr, dht_id=None):
  return serialization.Contact(gnutella_VENDOR,
                               gnutella_VERSION,
                               dht_id,
                               addr)
